package org.dromara.pay.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ProfilesUtils;
import org.dromara.pay.config.WxPayProperties;
import org.dromara.pay.constant.LogisticsType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 微信相关工具
 *
 * @author zlj
 * @since 2023/3/8
 */
@Component
@Slf4j
public class WxOrderUtil {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private WxPayProperties wxPayProperties;
    private static StringRedisTemplate staticRedisTemplate;

    /**
     * access_token获取计数器
     */
    private static Integer count = 0;
    /**
     * access_token获取失败尝试次数
     */
    private static final Integer TRY_COUNT = 10;

    @PostConstruct
    public void init() {
        staticRedisTemplate = redisTemplate;
        XCX_APP_ID = wxPayProperties.getMinAppId();
        XCX_APP_SECRET = wxPayProperties.getAppSecret();
    }

    private static String XCX_APP_ID;
    private static String XCX_APP_SECRET;
    private static final String XCX_UPLOAD_SHIPPING_INFO_URL = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info";
    private static final String XCX_GET_ORDER_STATUS_URL = "https://api.weixin.qq.com/wxa/sec/order/get_order";
    private static final String XCX_GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    private static final String REDIS_KEY_XCX_ACCESS_TOKEN = "xcx_access_token";

    private static final String XCX_SEND_SUBSCRIBE_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";


    /**
     * 查询订单状态
     *
     * @param transactionId 原支付交易对应的微信订单号
     */
    public static Boolean getOrderStatus(String transactionId) {
        Map<String, Object> map = new HashMap<>();
        map.put("transaction_id", transactionId);
        String response = null;
        JSONObject parseObj = null;
        try {
            response = HttpUtil.post(XCX_GET_ORDER_STATUS_URL + "?access_token=" + xcxGetAccessToken(), JSONUtil.toJsonStr(map));
            log.info("查询订单状态接口响应结果：" + JSONUtil.toJsonStr(response));
            parseObj = JSONUtil.parseObj(response);
            Integer errcode = parseObj.getInt("errcode");
            if (ObjectUtil.notEqual(errcode, 0)) {
                throw new ServiceException("系统繁忙");
            }
            Integer orderState = parseObj.getJSONObject("order").getInt("order_state");
            //订单状态枚举：(1) 待发货；(2) 已发货；(3) 确认收货；(4) 交易完成；(5) 已退款。
            return orderState.equals(4);
        } catch (Exception e) {
            Integer errcode = parseObj.getInt("errcode");
            if (errcode.equals(40001)) {
                staticRedisTemplate.delete(REDIS_KEY_XCX_ACCESS_TOKEN);
            }
            log.error("查询订单状态接口报错, 错误码：" + errcode);
            throw new ServiceException("系统繁忙,错误码:" + errcode);
        }
    }

    /**
     * 发货信息录入接口
     *
     * @param transactionId 原支付交易对应的微信订单号
     * @param logisticsType {@link LogisticsType}
     * @param itemDesc      商品信息，例如：微信红包抱枕*1个，限120个字以内
     * @return
     */
    public static void uploadShippingInfo(String transactionId, Integer logisticsType, String itemDesc, String openid) {
        Map<String, Object> orderKey = new HashMap<>();
        orderKey.put("order_number_type", 2);
        orderKey.put("transaction_id", transactionId);

        Map<String, Object> shippingList = new HashMap<>();
        shippingList.put("item_desc", itemDesc);

        Map<String, Object> payer = new HashMap<>();
        payer.put("openid", openid);

        Map<String, Object> map = new HashMap<>();
        map.put("order_key", orderKey);
        map.put("logistics_type", logisticsType);
        map.put("delivery_mode", 1);
        map.put("shipping_list", Arrays.asList(shippingList));
        map.put("upload_time", DateUtil.format(new Date(), "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"));
        map.put("payer", payer);
        String response = null;
        try {
            log.info("发货信息录入接口请求参数：" + JSONUtil.toJsonStr(map));
            response = HttpUtil.post(XCX_UPLOAD_SHIPPING_INFO_URL + "?access_token=" + xcxGetAccessToken(), JSONUtil.toJsonStr(map));
            Integer errcode = JSONUtil.parseObj(response).getInt("errcode");
            if (ObjectUtil.notEqual(errcode, 0)) {
                throw new ServiceException("系统繁忙");
            }
        } catch (Exception e) {
            Integer errcode = JSONUtil.parseObj(response).getInt("errcode");
            if (errcode.equals(40001)) {
                staticRedisTemplate.delete(REDIS_KEY_XCX_ACCESS_TOKEN);
                uploadShippingInfo(transactionId, logisticsType, itemDesc, openid);
            }
            log.error("发货信息录入接口报错, 错误码：" + errcode);
            throw new ServiceException("系统繁忙,错误码:" + errcode);
        }
    }

    /**
     * 小程序获取 access_token
     *
     * @return access_token
     */
    public static String xcxGetAccessToken() {
        //从缓存获取accessToken
        String accessToken = staticRedisTemplate.opsForValue().get(REDIS_KEY_XCX_ACCESS_TOKEN);
        if (StrUtil.isNotBlank(accessToken)) {
            return accessToken;
        }
        //缓存不存在, 重新申请
        Map<String, Object> param = new HashMap<>();
        param.put("appid", XCX_APP_ID);
        param.put("secret", XCX_APP_SECRET);
        param.put("grant_type", "client_credential");
        String response = HttpUtil.get(XCX_GET_ACCESS_TOKEN_URL, param);
        //解析
        JSONObject jsonObject = JSONUtil.parseObj(response);
        accessToken = jsonObject.getStr("access_token");
        if (StrUtil.isBlank(accessToken)) {
            count++;
            if (count > TRY_COUNT) {
                count = 0;
                throw new RuntimeException("获取access_token失败");
            }
            log.error("第" + count + "次尝试获取access_token");
            //获取失败重试
            xcxGetAccessToken();
        }
        count = 0;
        Integer expiresIn = jsonObject.getInt("expires_in");
        staticRedisTemplate.opsForValue().set(REDIS_KEY_XCX_ACCESS_TOKEN, accessToken, expiresIn - 300, TimeUnit.SECONDS);
        return accessToken;
    }

    /**
     * 发送订阅消息（参数优化版）
     *
     * @param openid 接收者ID
     * @param page   跳转页面
     */
    public static void sendSubscribeMessage(String openid, String page, Map<String, String> data) {
        Map<String, Object> params = new HashMap<>();
        params.put("template_id", "LygIpCmFE69qN9q-CL4FJuHybp1ZepgFhQqSON1G_pc");
        params.put("page", page);
        params.put("touser", openid);
        params.put("miniprogram_state", ProfilesUtils.check() ? "formal" : "developer");//跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
//        params.put("miniprogram_state", "developer");//跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版

        // 构建微信数据格式
        Map<String, Map<String, String>> contentData = new LinkedHashMap<>();
        data.forEach((key, value) ->
            contentData.put(key, Collections.singletonMap("value", value))
        );
        params.put("data", contentData);

        // 发送请求并处理响应
        String response = HttpUtil.post(XCX_SEND_SUBSCRIBE_MESSAGE_URL + "?access_token=" + xcxGetAccessToken(),
            JSONUtil.toJsonStr(params));
        handleSubscribeResponse(response);
    }

    /**
     * 处理订阅消息响应（增强版）
     */
    private static void handleSubscribeResponse(String response) {
        if (StrUtil.isBlank(response)) {
            throw new ServiceException("微信接口无响应");
        }

        JSONObject json = JSONUtil.parseObj(response);
        Integer errcode = json.getInt("errcode");
        if (errcode == null || errcode != 0) {
            log.error("微信订阅消息失败：{}", json);
            // Token失效自动刷新重试
            if (errcode == 40001 || errcode == 41001) {
                staticRedisTemplate.delete(REDIS_KEY_XCX_ACCESS_TOKEN);
                handleSubscribeResponse(response);
                return;
            }
            throw new ServiceException("消息发送失败: " + json.getStr("errmsg"));
        }
        log.info("订阅消息发送成功：{}", json);
    }
}
